#include "stdafx.h"
#include "ShellFolderViewImpl.h"
#include "Guid.h"
#include <algorithm>

namespace SyncClient
{
	enum _FVITEMTYPE
	{
		FVITEMTYPE_ROOT = 0x01,
		FVITEMTYPE_MYFILE = 0x02,
		FVITEMTYPE_MYSPACE = 0X04,
		FVITEMTYPE_INVALID = 0x0
	};
	typedef DWORD FVITEMTYPE;

	typedef struct _FVITEMID
	{
		USHORT cb;
		FVITEMTYPE dwType;
		BYTE szName[1];
	}FVITEMID;

	class ShellFolderViewImpl::Impl : 
		public IShellFolder2, 
		public IPersistFolder2
	{
	public:
		Impl()
			:m_cRef(0)
			,m_pidl(NULL)
		{

		}

		virtual ~Impl()
		{
			if (m_pidl)
			{
				::CoTaskMemFree(m_pidl);
			}
		}

		// IUnknown
		IFACEMETHODIMP QueryInterface(REFIID riid, void ** ppv)
		{
#pragma warning(push)
#pragma warning(disable:4838)
			static const QITAB qit[] =
			{
				QITABENT(Impl, IShellFolder),
				QITABENT(Impl, IShellFolder2),
				QITABENT(Impl, IPersist),
				QITABENT(Impl, IPersistFolder),
				QITABENT(Impl, IPersistFolder2),
				{ 0 }
			};
#pragma warning(pop)
			return QISearch(this, qit, riid, ppv);
		}

		IFACEMETHODIMP_(ULONG) AddRef()
		{
			return InterlockedIncrement(&m_cRef);
		}

		IFACEMETHODIMP_(ULONG) Release()
		{
			return InterlockedDecrement(&m_cRef);
		}

		// IShellFolder
		IFACEMETHODIMP ParseDisplayName(HWND hwnd, IBindCtx * pbc, LPWSTR pszDisplayName, ULONG * pchEaten, PIDLIST_RELATIVE * ppidl, ULONG * pdwAttributes)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP EnumObjects(HWND hwnd, SHCONTF grfFlags, IEnumIDList ** ppenumIDList)
		{
			CComPtr<ShellFolderViewEnumIDListImpl> penum(new (std::nothrow) ShellFolderViewEnumIDListImpl(this, hwnd, grfFlags));
			if (!penum)
			{
				return E_OUTOFMEMORY;
			}
			HRESULT hr = penum->QueryInterface(IID_PPV_ARGS(ppenumIDList));
			if (FAILED(hr))
			{
				return hr;
			}
			return S_OK;
		}

		IFACEMETHODIMP BindToObject(PCUIDLIST_RELATIVE pidl, IBindCtx * pbc, REFIID riid, void ** ppv)
		{
			HRESULT hr = S_FALSE;
			
			CComPtr<Impl> pShellFolderView(new (std::nothrow)Impl);
			if (NULL == pShellFolderView)
			{
				hr = E_OUTOFMEMORY;
				return hr;
			}

			CAutoItemIDLIST pidlFirst = ILCloneFirst(pidl);
			if (!pidlFirst)
			{
				return E_OUTOFMEMORY;
			}
			CAutoItemIDLIST pidlBind = ILCombine(m_pidl, (LPCITEMIDLIST)pidlFirst.GetPointer());
			if (!pidlBind)
			{
				return E_OUTOFMEMORY;
			}
			hr = pShellFolderView->Initialize((LPCITEMIDLIST)pidlBind.GetPointer());
			if (FAILED(hr))
			{
				return hr;
			}
			PCUIDLIST_RELATIVE pidlNext = ILNext(pidl);
			if (ILIsEmpty(pidlNext))
			{
				// If we're reached the end of the idlist, return the interfaces we support for this item.
				// Other potential handlers to return include IPropertyStore, IStream, IStorage, etc.
				hr = pShellFolderView->QueryInterface(riid, ppv);
			}
			else
			{
				// Otherwise we delegate to our child folder to let it bind to the next level.
				hr = pShellFolderView->BindToObject(pidlNext, pbc, riid, ppv);
			}
			return hr;
		}

		IFACEMETHODIMP BindToStorage(PCUIDLIST_RELATIVE pidl, IBindCtx * pbc, REFIID riid, void ** ppv)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP CreateViewObject(HWND hwndOwner, REFIID riid, void ** ppv)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, SFGAOF * rgfInOut)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * rgfReserved, void ** ppv)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF uFlags, STRRET * pName)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP SetNameOf(HWND hwnd, PCUITEMID_CHILD pidl, LPCWSTR pszName, SHGDNF uFlags, PITEMID_CHILD * ppidlOut)
		{
			return E_NOTIMPL;
		}

		// IShellFolder2
		IFACEMETHODIMP GetDefaultSearchGUID(GUID * pguid)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP EnumSearches(IEnumExtraSearch ** ppenum)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG * pSort, ULONG * pDisplay)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetDefaultColumnState(UINT iColumn, SHCOLSTATEF * pcsFlags)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID * pscid, VARIANT * pv)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS * psd)
		{
			return E_NOTIMPL;
		}

		IFACEMETHODIMP MapColumnToSCID(UINT iColumn, SHCOLUMNID * pscid)
		{
			return E_NOTIMPL;
		}

		// IPersist
		IFACEMETHODIMP GetClassID(CLSID * pClassID)
		{
			*pClassID = CLSID_ShellFolderViewImpl;
			return S_OK;
		}

		// IPersistFolder
		IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidl)
		{
			m_pidl = ILCloneFull(pidl);
			return m_pidl ? S_OK : E_FAIL;
		}

		// IPersistFolder2
		IFACEMETHODIMP GetCurFolder(PIDLIST_ABSOLUTE * ppidl)
		{
			if (!m_pidl)
			{
				return E_FAIL;
			}
			*ppidl = ILCloneFull(m_pidl);
			if (!(*ppidl))
			{
				return E_OUTOFMEMORY;
			}
			return S_OK;
		}

	private:
		class ShellFolderViewEnumIDListImpl : public IEnumIDList
		{
		public:
			ShellFolderViewEnumIDListImpl(Impl *pShellFolderView, HWND hwndOwner, SHCONTF grfFlags)
				: m_cRef(0)
				, m_pShellFolderView(pShellFolderView)
				, m_hwndOwner(hwndOwner)
				, m_grfFlags(grfFlags)
				, m_nPage(0)
				, m_nCurPos(0)
				, m_bNomoreData(false)
			{

			}

			virtual ~ShellFolderViewEnumIDListImpl()
			{

			}

			// IUnknown methods
			IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
			{
#pragma warning(push)
#pragma warning(disable:4838)
				static const QITAB qit[] = {
					QITABENT(ShellFolderViewEnumIDListImpl, IEnumIDList),
					{ 0 }
				};
#pragma warning(pop)
				return QISearch(this, qit, riid, ppv);
			}

			IFACEMETHODIMP_(ULONG) AddRef()
			{
				if (m_pShellFolderView)
				{
					m_pShellFolderView->AddRef();
				}
				return InterlockedIncrement(&m_cRef);
			}

			IFACEMETHODIMP_(ULONG) Release()
			{
				if (m_pShellFolderView)
				{
					m_pShellFolderView->Release();
				}
				long cRef = InterlockedDecrement(&m_cRef);
				if (0 == cRef)
				{
					delete this;
				}
				return cRef;
			}

			// IEnumIDList
			IFACEMETHODIMP Next(ULONG celt, PITEMID_CHILD *rgelt, ULONG *pceltFetched)
			{
				if (celt == 0 || m_pShellFolderView == NULL)
				{
					return S_FALSE;
				}
				PIDLIST_ABSOLUTE pabidlist;
				HRESULT hr = m_pShellFolderView->GetCurFolder(&pabidlist);
				if (FAILED(hr))
				{
					return hr;
				}
				if (m_nCurPos == 0)
				{
					// special folders

					// common folders
				}

				return E_NOTIMPL;
			}

			IFACEMETHODIMP Skip(DWORD celt)
			{
				return E_NOTIMPL;
			}

			IFACEMETHODIMP Reset()
			{
				return E_NOTIMPL;
			}

			IFACEMETHODIMP Clone(IEnumIDList **ppenum)
			{
				CComPtr<ShellFolderViewEnumIDListImpl> penum(new (std::nothrow) ShellFolderViewEnumIDListImpl
				(m_pShellFolderView, m_hwndOwner, m_grfFlags));
				penum->m_nPage = this->m_nPage;
				penum->m_nCurPos = this->m_nCurPos;
				penum->m_bNomoreData = this->m_bNomoreData;
				std::for_each(this->m_items.begin(), this->m_items.end(), [&](auto &item) {
					penum->m_items.push_back(item);
				});
				return penum->QueryInterface(IID_PPV_ARGS(ppenum));
			}

		private:
			ULONG m_cRef;
			Impl *m_pShellFolderView;
			HWND m_hwndOwner;
			SHCONTF m_grfFlags;
			uint32_t m_nPage;
			uint32_t m_nCurPos;
			std::list<std::wstring> m_items;
			bool m_bNomoreData;

			static const uint32_t PAGE_SIZE = 1000;
		};

	private:
		ULONG m_cRef;
		PIDLIST_ABSOLUTE m_pidl;
	};

	ShellFolderViewImpl::ShellFolderViewImpl()
		:impl_(new (std::nothrow)Impl)
	{
	}

	ShellFolderViewImpl::~ShellFolderViewImpl()
	{
	}

	IFACEMETHODIMP ShellFolderViewImpl::QueryInterface(REFIID riid, void ** ppv)
	{
		if (!impl_)
		{
			return E_OUTOFMEMORY;
		}
		return impl_->QueryInterface(riid, ppv);
	}

	IFACEMETHODIMP_(ULONG) ShellFolderViewImpl::AddRef()
	{
		if (!impl_)
		{
			return 0;
		}
		return impl_->AddRef();
	}

	IFACEMETHODIMP_(ULONG) ShellFolderViewImpl::Release()
	{
		if (!impl_)
		{
			return 0;
		}
		return impl_->Release();
	}
}
